﻿using System;
using System.Collections.Generic;
#if LOG
using System.Diagnostics;
#endif
using System.Linq;
using Common;

namespace TeamIce
{

	//надежность паса
	public enum Safeness
	{
		Unknown,	//не рассчитана
		Impossible, //пас отдавать нельзя
		Risky,		//пас рискованный - 68.2% успеха (1 сигма)
		Safe		//пас надежный - 95.4% успеха (2 сигмы)
	}

	public class PlayerEx
    {
		//базовый игрок
        public IcePlayer Player;
		//точка игрока, относительно мяча (как если бы мяч был в начале координат)
        public Vector RelPos;
    	public float BallDistance;
    	private readonly Vector ballPosition;
		public readonly double VelToBall;
        public readonly double VelToSide;
		public readonly bool VelToSideDirectedToBall;

        //для скоростей от PlayerMinShootDistance до PlayerMaxShootDistance
        //информация об углах в которых игрок достанет мяч, при отсутствии соперников
        //об углах в которых игрок достанет мяч, а соперники нет
        //о надежности паса, о возможности удара по воротам и т.д.
        public PlayerExInfo [] Info = new PlayerExInfo[11];

		public PlayerExInfo[] ReflectInfo = new PlayerExInfo[6];

		//Команда игрока: 1 - наша, 2 - соперника
        public int TeamId;
		//оценка паса игроку (только для нашей команды)
    	public int PassScore;
    	public Safeness BestPassSafeness;
    	public int BestPassStrength;
		public Vector BestPassTarget;

    	public int ShootScore;
    	public Safeness BestShootSafeness;
    	public int BestShootStrength;
    	public Vector BestShootTarget;

        public bool IsPassSectorsFilled;
        public bool IsShootSectorsFilled;
    	public bool IsReflectPassFilled;

    	public Sector SectorForOpen;

		//Возвращает наибольший свободный сектор из диапазона от alphaFrom до alphaTo
		//alphaFrom и alphaTo должны быть от -Pi до Pi или от 0 до 2Pi
		public static Sector BiggestFreeSector(double alphaFrom, double alphaTo, IEnumerable<PlayerEx> enemies, int str)
		{
			if (double.IsNaN(alphaFrom) || double.IsNaN(alphaTo))
				return null;

			Sector diapason = new Sector(-Math.PI, Math.PI);
			if (!diapason.IsAngleInSector(alphaFrom) ||
				!diapason.IsAngleInSector(alphaTo))
			{
				diapason.AlphaFrom = 0;
				diapason.AlphaTo = 2*Math.PI;
#if LOG
				Debug.Assert(diapason.IsAngleInSector(alphaFrom) && diapason.IsAngleInSector(alphaTo));
#endif
			}

			List<double> angles = new List<double>(14) { alphaFrom, alphaTo };

			List<PlayerEx> intersectEnemies =
				(from enemyEx in enemies
				 where
					enemyEx.TeamId == 2 &&
					IceMath.Intersect(alphaFrom, alphaTo,
							  enemyEx.Info[str].CatchSector.AlphaFrom, enemyEx.Info[str].CatchSector.AlphaTo)
				 select enemyEx).ToList();

			foreach (PlayerEx enemyEx in intersectEnemies)
			{
				double a = enemyEx.Info[str].CatchSector.AlphaFrom;
				while (a < diapason.AlphaFrom) a += 2*Math.PI;
				while (a > diapason.AlphaTo) a -= 2*Math.PI;
				if (a > alphaFrom && a < alphaTo)
					angles.Add(a);

				a = enemyEx.Info[str].CatchSector.AlphaTo;
				while (a < diapason.AlphaFrom) a += 2*Math.PI;
				while (a > diapason.AlphaTo) a -= 2*Math.PI;
				if (a > alphaFrom && a < alphaTo)
					angles.Add(a);
			}

			angles.Sort();

			List<Sector> freeSectors = new List<Sector>(10);
			for (int i = 0; i < angles.Count - 1; i++)
			{
				if (!intersectEnemies.Any(
					enemyEx => IceMath.Intersect(angles[i] + IceConst.EPS, angles[i + 1] - IceConst.EPS,
						enemyEx.Info[str].CatchSector.AlphaFrom, enemyEx.Info[str].CatchSector.AlphaTo)))
					freeSectors.Add(new Sector(angles[i], angles[i + 1]));
			}

			if (freeSectors.Count == 0)
				return null;
			freeSectors.Sort(Sector.CompareBySizeDesc);
			return freeSectors[0];
		}

        #region FillCatchSectors
        private void FillCatchSectorForStrength(int strength)
		{
			int dist = (int) BallDistance;

            if (TeamId == 2 && BallDistance < Constants.BallMaxPickUpDistance * 1.5)
                return;

            //Для близкостоящих проверяем успеет мяч проскочить мимо игрока за время BallShootTimer
		    Vector ballPosAfterTimer = IceMath.CalcBallPos(ballPosition, RelPos.Unit()*IceConst.GetBallVelocityByShootStrength(strength),
		                                           Constants.BallShootTimer, false);
            if (dist < ballPosAfterTimer.GetDistanceTo(ballPosition))
            {
                Vector playerPos = IceMath.CalcPlayerMovementPos(Player.Position, Player.Velocity, ballPosAfterTimer,
                                                                 Constants.BallShootTimer);
                //Если вообще не успеет схватить мяч - то вообще не учитываем
                if (playerPos.GetDistanceTo(ballPosAfterTimer) > Constants.BallMaxPickUpDistance &&
                    playerPos.GetDistanceTo(ballPosition) < ballPosAfterTimer.GetDistanceTo(ballPosition))
                    return;
            }

			if (Player.FallenTimer > 0)
			{
				//Если игрок лежит, то радиус для него рассчитываем, как если бы он был ближе
				//на расстояние, которое мяч пройдет за время пока он лежит.
				//А относительную позицию оставляем прежней, таким образом его CatchSector уменьшится
				dist -=
					(int) IceMath.CalcBallPos(Vector.Zero,
					                  new Vector(strength/Constants.PlayerMaxShootStr*Constants.BallMaxVelocity, 0),
					                  Player.FallenTimer).Length;
				if (dist <= 0)
					return;
			}

			int radius = IceConst.GetPlayerRadiusWithStartVelocity(dist, strength, VelToBall);    

			double alphaFrom;
			double alphaTo;

    		IceMath.RadiusToAngles(radius, RelPos, out alphaFrom, out alphaTo);

			double delta = IceConst.GetAngleDelta(dist, strength, VelToSide, VelToSideDirectedToBall);
			alphaFrom += delta;
			alphaTo += delta;

			//Стараемся, чтобы углы были в диапазоне от -Пи до Пи
			//Сначала гарантируем что alphaFrom в нужном диапазоне
			while (alphaFrom < -Math.PI)
			{
				alphaFrom += 2*Math.PI;
				alphaTo += 2*Math.PI;
			}

			while (alphaFrom > Math.PI)
			{
				alphaFrom -= 2 * Math.PI;
				alphaTo -= 2 * Math.PI;
			}
			
#if LOG
			Debug.Assert(alphaTo >= -Math.PI);
			if (alphaTo > Math.PI)
			{
				//Если диапазон от -Пи до Пи не получается, то делаем от 0 до Пи
				Debug.Assert(alphaFrom >= 0);
				Debug.Assert(alphaTo <= 2*Math.PI);
			}
#endif

			Info[strength].CatchSector.AlphaFrom = alphaFrom;
			Info[strength].CatchSector.AlphaTo = alphaTo;
		}

		//Заполняет углы для скоростей от PlayerMinShootDistance до PlayerMaxShootDistance, между которыми игрок поймает мяч
		//если он будет пущен точно в направлении игрока с данной скоростью
    	public void FillCatchSectors()
    	{
    		if (Math.Abs(RelPos.Y) < IceConst.EPS && Math.Abs(RelPos.X) < IceConst.EPS)
    			return;

			for (int passStrength = IceConst.PlayerMinShootStr; passStrength <= IceConst.PlayerMaxShootStr; passStrength++)
				FillCatchSectorForStrength(passStrength);	
    	}
		#endregion 

		#region FillPassSectors
		private void FillSectorForOpen(int str, IEnumerable<PlayerEx> enemies)
		{
			bool openBack = ballPosition.X > IceConst.BackPositionsForOpenLine;

			List<PlayerEx> enemiesList = enemies.ToList();

		    double alphaFrom = double.NaN;
			double alphaTo = double.NaN;
			switch (Player.PlayerType)
			{
				case PlayerType.LeftDefender:
					alphaFrom = 3*Math.PI/4;
					alphaTo = Math.PI/4;
					break;
				case PlayerType.RightDefender:
					alphaFrom = -3*Math.PI/4;
					alphaTo = -Math.PI/4;
					break;
				case PlayerType.LeftForward:
					if (!openBack)
					{
						alphaFrom = 0;
						alphaTo = Math.PI/2;
					}
					else
					{
						alphaFrom = Math.PI/2;
						alphaTo = Math.PI;
					}
					break;
				case PlayerType.RightForward:
					if (!openBack)
					{
						alphaFrom = -Math.PI/2;
						alphaTo = 0;
					}
					else
					{
						alphaFrom = -Math.PI;
						alphaTo = -Math.PI/2;
					}
					break;
				case PlayerType.CenterForward:
					
					if (!openBack)
					{
						alphaFrom = -Math.PI/4;
						alphaTo = Math.PI/4;
					}
					else
					{
						alphaFrom = 3*Math.PI/4;
						alphaTo = 5*Math.PI/4;
					}
					break;
			}

			SectorForOpen = BiggestFreeSector(alphaFrom, alphaTo, enemiesList, str);
		}

    	private void FillPassSectorForStrength(IEnumerable<PlayerEx> enemies, int str)
		{
			Info[str].PassSafeness = Safeness.Impossible;

            Info[str].PassSector =
				BiggestFreeSector(Info[str].CatchSector.AlphaFrom, Info[str].CatchSector.AlphaTo, enemies.ToList(), str);

            if (Info[str].PassSector == null)
                return;

			Info[str].PassTarget = IceMath.BoundPointOnAngle(ballPosition, Info[str].PassSector.Average, (int)(Player.Position.X - ballPosition.X));
		    Info[str].PassSafeness = IceConst.GetSectorSafeness(Info[str].PassSector, str);
		}

		public static bool EnemyCanIntercept(PlayerEx enemy, PlayerEx friend, Vector ballPos, int str)
		{
			Sector enemySector = enemy.Info[str].CatchSector;
			Sector friendSector = friend.Info[str].CatchSector;

			if (!IceMath.Intersect(enemySector, friendSector))
				return false;

			//Если соперник ближе к мячу, то не рискуем, считаем его опасным
			if (enemy.BallDistance < friend.BallDistance && enemy.Player.FallenTimer <= 0)
				return true;

            var alpha = FindOptimalEnemyAlpha(enemySector, friendSector);
			//Посылаем мяч по оптимальной траектории и смотрим за сколько ходов он его поймает
            float shootStrength = IceConst.GetBallVelocityByShootStrength(str);
            Vector dest = IceMath.PointOnAngle(ballPos, alpha);
            Vector ballVel = (dest - ballPos).Unit() * shootStrength;
		    VectorSteps enemyCatchPos =
		        IceMath.CatchBallWithConstVelocities(ballPos, ballVel*IceConst.AverageBallSlowdown, enemy.Player.Position,
		                                             IceConst.AveragePlayerSpeed);
			if (enemy.Player.FallenTimer > 0)
				enemyCatchPos.Steps += enemy.Player.FallenTimer;

            //Считаем за сколько ходов мяч догонит свой игрок
		    VectorSteps friendCatchPos =
		        IceMath.CatchBallWithConstVelocities(ballPos, ballVel*IceConst.AverageBallSlowdown, friend.Player.Position,
		                                             IceConst.AveragePlayerSpeed);
			if (friend.Player.FallenTimer > 0)
				friendCatchPos.Steps += friend.Player.FallenTimer;

            return enemyCatchPos.Steps <= friendCatchPos.Steps;
        }

		//Возвращает угол - оптимальную траекторию для соперника, по которой он поймает мяч быстрее всего
		//Угол должен входить в оба сектора
    	public static double FindOptimalEnemyAlpha(Sector enemySector, Sector friendSector)
    	{
			double enemyFrom = enemySector.AlphaFrom;
			double enemyTo = enemySector.AlphaTo;

			double friendFrom = friendSector.AlphaFrom;
			double friendTo = friendSector.AlphaTo;

			//Считаем оптимальной траекторией для соперника биссектрису его угла
    		double alpha = enemySector.Average;
    		if (!friendSector.IsAngleInSector(alpha))
    		{
    			//траектория не попала в угол своего игрока
    			//ищем ближайшую к биссектрисе граничную траекторию
    			List<double> angles = new List<double>(4);
    			//Добавляем углы, которые попадают в оба сектора
    			if (enemySector.IsAngleInSector(friendFrom))
    				angles.Add(friendFrom);
    			if (enemySector.IsAngleInSector(friendTo))
    				angles.Add(friendTo);
    			if (friendSector.IsAngleInSector(enemyFrom))
    				angles.Add(enemyFrom);
    			if (friendSector.IsAngleInSector(enemyTo))
    				angles.Add(enemyTo);

    			double bestAngle = double.NaN;
    			double min = double.MaxValue;
    			foreach (double angle in angles)
    			{
    				if (min <= Math.Abs(angle - alpha)) continue;
    				min = Math.Abs(angle - alpha);
    				bestAngle = angle;
    			}
    			if (double.IsNaN(bestAngle))
    				return alpha;
    			alpha = bestAngle;
    		}
    		return alpha;
    	}

    	//Заполняет углы для всех скоростей, 
		//в которых игрок сможет получить пас, а соперники не смогут помешать
		public void FillPassSectors(List<PlayerEx> enemies)
		{
			if (IsPassSectorsFilled) return;

            for (int passStrength = IceConst.PlayerMinShootStr; passStrength <= IceConst.PlayerMaxShootStr; passStrength++)
            {
                int strength = passStrength;

                List<PlayerEx> closeEnemies =
					enemies.Where(enemy => EnemyCanIntercept(enemy, this, ballPosition, strength)).ToList();

                FillPassSectorForStrength(closeEnemies, passStrength);

                //Для минимальной скорости заполняем еще и диапазон, куда идти, чтобы открыться для получения паса
                if (passStrength == IceConst.PlayerMinShootStr)
                    FillSectorForOpen(passStrength, closeEnemies);
            }
		    IsPassSectorsFilled = true;
		}

		public void FillPassScore(Vector ballPos, Vector playerPos, List<PlayerEx> enemyTeam, int passDistScore = -1)
		{
			BestPassSafeness = Safeness.Impossible;
			//for (int i = IceConst.PlayerMinShootStr; i <= IceConst.PlayerMaxShootStr; i++)
            for (int i = IceConst.PlayerMaxShootStr; i >= IceConst.PlayerMinShootStr; i--)
			{
				if (Info[i].PassSafeness == Safeness.Safe)
				{
					BestPassSafeness = Safeness.Safe;
					BestPassStrength = i;
					BestPassTarget = Info[i].PassTarget;
					PassScore = GetSummaryScore(playerPos, ballPos, enemyTeam, passDistScore);
					break;
				}
				if (Info[i].PassSafeness != Safeness.Risky || BestPassSafeness != Safeness.Impossible) continue;
				BestPassSafeness = Safeness.Risky;
				BestPassStrength = i;
				BestPassTarget = Info[i].PassTarget;
				PassScore = GetSummaryScore(playerPos, ballPos, enemyTeam, passDistScore);
			}
		}
		#endregion

		#region FillShootSectors
		private void FillShootSectorsForStrength(List<PlayerEx> enemies, int str)
        {
            Line lineTop = new Line(Player.Position, Field.EnemyGoal.Top);
            Line lineBottom = new Line(Player.Position, Field.EnemyGoal.Bottom);

            double alphaFrom = lineBottom.Angle;
            double alphaTo = lineTop.Angle;

            Info[str].ShootSector = BiggestFreeSector(alphaFrom, alphaTo, enemies, str);
            if (Info[str].ShootSector == null)
                return;
            Info[str].ShootTarget = IceMath.BoundPointOnAngle(Player.Position, Info[str].ShootSector.Average);
            Info[str].ShootSafeness = IceConst.GetSectorSafeness(Info[str].ShootSector, str);
        }
		
		//копирует сектора для ударов с другого игрока
		public void CopyShootSectors(PlayerEx source)
		{
			for (int str = IceConst.PlayerMinShootStr; str <= IceConst.PlayerMaxShootStr; str++)
			{
				if (source.Info[str].ShootSector == null) continue;
				Info[str].ShootSector.AlphaFrom = source.Info[str].ShootSector.AlphaFrom;
				Info[str].ShootSector.AlphaTo = source.Info[str].ShootSector.AlphaTo;
				Info[str].ShootTarget = source.Info[str].ShootTarget;
				Info[str].ShootSafeness = source.Info[str].ShootSafeness;
			}
		}

		//Заполняет сектора для ударов по воротам
        public void FillShootSectors(List<PlayerEx> enemies)
        {
            if (IsShootSectorsFilled) return;

            if (Vector.Distance(Player.Position, Field.EnemyGoal) > IceConst.MaxShootDistance)
            {
                IsShootSectorsFilled = true;
                return;
            }

            List<PlayerEx> closeEnemies = enemies.ToList();

            for (int passStrength = IceConst.PlayerMinShootStr; passStrength <= IceConst.PlayerMaxShootStr; passStrength++)
                FillShootSectorsForStrength(closeEnemies, passStrength);

        	FillShootScore();

            IsShootSectorsFilled = true;
        }

		public void FillShootScore()
		{
			BestShootSafeness = Safeness.Impossible;
			for (int i = IceConst.PlayerMinShootStr; i <= IceConst.PlayerMaxShootStr; i++)
			{
				if (Info[i].ShootSafeness == Safeness.Safe)
				{
					BestShootSafeness = Safeness.Safe;
					BestShootStrength = i;
					BestShootTarget = Info[i].ShootTarget;
					ShootScore = IceConst.SafeShootScore;
					break;
				}
				if (Info[i].ShootSafeness != Safeness.Risky || BestShootSafeness != Safeness.Impossible) continue;
				BestShootSafeness = Safeness.Risky;
				BestShootStrength = i;
				BestShootTarget = Info[i].ShootTarget;
				ShootScore = IceConst.RiskyShootScore;
			}
		}
		#endregion

		/*#region ReflectPass
		public void FillReflectPassForStrength(List<PlayerEx> enemies, int str)
		{
			Vector ballVel = (ReflectPoint - ballPosition).Unit()*IceConst.GetBallVelocityByShootStrength(str);

			List<Pair<PlayerEx, int>> enemiesCatch = new List<Pair<PlayerEx, int>>(enemies.Count);
			int reflectSteps = (int)Math.Ceiling(Vector.Distance(ballPosition, ReflectPoint) / ballVel.Length);
			foreach (PlayerEx playerEx in enemies)
			{
				VectorSteps vs = IceMath.CatchBallWithConstVelocities(ballPosition, ballVel, playerEx.Player.Position, IceConst.AveragePlayerSpeed);
				Pair<PlayerEx, int> enemyCatch = new Pair<PlayerEx, int>(playerEx, vs.Steps);
				if (enemyCatch.Second < reflectSteps)
					return;
			}
		}

		public void FillReflectPass(List<PlayerEx> enemies)
		{
			if (IsReflectPassFilled) return;
			ReflectPoint = IceMath.ClosestReflectPoint(ballPosition, Player.Position);
			for (int passStrength = IceConst.PlayerMinShootStr; passStrength <= IceConst.PlayerMaxShootStr; passStrength++)
			{
				int strength = passStrength;

				//List<PlayerEx> closeEnemies =	enemies.Where(enemy => EnemyCanIntercept(enemy, this, ballPosition, strength)).ToList();

				FillReflectPassForStrength(enemies, passStrength);
			}

			IsReflectPassFilled = true;
		}
		#endregion*/

		public PlayerEx(Player player, Vector ballPos, int teamId)
			: this(new IcePlayer(player), ballPos, teamId)
		{}

		public PlayerEx(IcePlayer player, Vector ballPos, int teamId)
        {
            Player = player;
            RelPos = player.Position - ballPos;
        	BallDistance = RelPos.Length;
            TeamId = teamId;
        	IsPassSectorsFilled = false;
            IsShootSectorsFilled = false;
			IsReflectPassFilled = false;
			BestPassSafeness = Safeness.Unknown;
        	BestPassStrength = 0;
			BestShootSafeness = Safeness.Unknown;
			BestShootStrength = 0;
			ballPosition = ballPos;
			SectorForOpen = new Sector(double.NaN, double.NaN);

            //velToBall > 0 - игрок идет к мячу, velToBall < 0 - от мяча
            if (Player.FallenTimer <= 0)
            {
                VelToBall = -Line.GetVectorProjection(Player.Velocity, Vector.Zero, RelPos);
                VelToSide = Line.GetVectorProjection(Player.Velocity, Vector.Zero, IceMath.GetPerpend(RelPos));
                //VelToSide = Math.Abs(VelToSide) * IceMath.IsVector2ClockwiseFromVector1(RelPos, Player.Velocity);
				VelToSide = Math.Abs(VelToSide) * IceMath.GetVelToSideSign(RelPos, Player.Velocity);
				VelToSideDirectedToBall = IceMath.IsVelToSideDirectedToBall(RelPos, Player.Velocity);
            }

			for (int i = IceConst.PlayerMinShootStr; i <= IceConst.PlayerMaxShootStr; i++)
                Info[i] = new PlayerExInfo();

			FillCatchSectors();
        }

		public static int CompareByDistanceToBall(PlayerEx p1, PlayerEx p2)
		{
			if (p1.RelPos.LengthSquared > p2.RelPos.LengthSquared) return 1;
			if (p1.RelPos.LengthSquared < p2.RelPos.LengthSquared) return -1;
			return 0;
		}

#region Score routines
        public static int CompareByScore(PlayerEx p1, PlayerEx p2)
		{
			if (p1.PassScore < p2.PassScore) return 1;
			if (p1.PassScore > p2.PassScore) return -1;
			return 0;
		}

		//passDistScore:
		//-1 - рассчитать оценку дальности паса
		//0..10 - уже рассчитаная оценка для движения
        public int GetSummaryScore(Vector playerPos, Vector ballPos, List<PlayerEx> enemyTeam, int passDistScore = -1)
        {
            return
                GetPositionScore(playerPos) * IceConst.ScoreKoefPosition +
                GetFreedomScore(playerPos, enemyTeam) * IceConst.ScoreKoefFreedom +
				(passDistScore == -1 ? GetPassDistScore(playerPos, ballPos) : passDistScore) * IceConst.ScoreKoefPassDist +
                ShootScore * IceConst.ScoreKoefCanShoot;
        }

		//http://img.leprosorium.com/1462333
    	public static int GetPositionScore(Vector position)
    	{
    		float zoneWidth = Field.Borders.Width/8;
    		float zoneHeight = Field.Borders.Height/7;
    		int zoneNum = (int) (position.X - IceConst.EPS)/(int) zoneWidth + 2;
    		int score = zoneNum;
			if (position.Y < zoneHeight || position.Y > Field.Borders.Height - zoneHeight)
				score -= zoneNum < 9 ? 1 : 2;

			if (zoneNum < 3)
			{
				//Штраф за пас назад
				score -= 5;
			}

			if (zoneNum == 8)
				if (position.X > 7 * zoneWidth &&
					position.Y > 3 * zoneHeight &&
					position.Y < 5 * zoneHeight)
					score += 1;
			if (zoneNum == 9)
			{
				if ((position.Y > zoneHeight && position.Y < 2 * zoneHeight) ||
					(position.Y > 5 * zoneHeight && position.Y < 6 * zoneHeight))
					score -= 1;
				else if (position.X < Field.Borders.Width - zoneWidth / 2)
					score += 1;

			}
    		return score;
    	}

    	public static int GetFreedomScore(Vector position, List<PlayerEx> enemyTeam)
    	{
    		int minDist =
    			(int)
    			(from enemy in enemyTeam
    			 select enemy.Player.GetDistanceTo(position)).Min();
    		int score = minDist/(int) Constants.PlayerMaxTackleDistance;

    		return score > 10 ? 10 : score;
    	}

    	public static int GetPassDistScore(Vector position, Vector ballPos)
    	{
    		float dist = position.GetDistanceTo(ballPos);
			if (dist < IceConst.BestPassDistance*0.9)
				return (int)(dist/IceConst.BestPassDistance*0.1);
    		return dist > IceConst.BestPassDistance*1.1
    		       	? Math.Max(0, 10 - (int) ((dist - IceConst.BestPassDistance)/IceConst.BestPassDistance*0.1))
    		       	: 10;
    	}
#endregion
    }
}
